﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;
using System.Text.RegularExpressions;
using System.Windows.Threading;
using PSE.Framework.UI.Controls.SmartClient.Validation;

namespace PSE.Framework.UI.Controls.SmartClient
{
    [DefaultProperty("Value")]
    public abstract class FixedMaskTextBox : BaseFilteredTextBox
    {
        #region Propriedades de controle de Estado
        private bool _isInternalValueChanged = false;
        protected bool IsInternalValueChanged
        {
            get { return _isInternalValueChanged; }
            set { _isInternalValueChanged = value; }
        }
        #endregion

        #region Constantes
        protected const string FormatExceptionErroMessage = "Unable to update the property \"Value\". The input data was invalid.";

        #endregion
        #region Propriedade
        /// <summary>
        /// Propriedade utilizada para indicar a Expressao regular
        /// a ser utilizada para validar a entrada de valor pelo propriedade
        /// Value
        /// </summary>
        private string _assignedFormatExpressio;
        public string AssignedValueFormatExpression
        {
            get { return _assignedFormatExpressio; }
            set { _assignedFormatExpressio = value; }
        }

        private string _separators = "";
        protected string Separators
        {
            get { return _separators; }
            set { _separators = value; }
        }

        private int _valueLength = 0;
        public int ValueLength
        {
            get { return _valueLength; }
            set { _valueLength = value; }
        }

        protected static readonly DependencyProperty InternalValueProperty =
            DependencyProperty.Register("InternalValue", typeof(string), typeof(FixedMaskTextBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnInternalValuePropertyChanged, CoerceInternalValuePropery));//, false, System.Windows.Data.UpdateSourceTrigger.LostFocus));

        protected string InternalValue
        {
            get
            {
                return (string)GetValue(InternalValueProperty);
            }
            set
            {
                SetValue(InternalValueProperty, value);
            }
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(string), typeof(FixedMaskTextBox), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValuePropertyChanged, null, false, System.Windows.Data.UpdateSourceTrigger.LostFocus));

        public string Value
        {
            get
            {
                return (string)GetValue(ValueProperty);
            }
            set
            {
                SetValue(ValueProperty, value);
            }
        }



        protected bool AutoFillBase
        {
            get { return (bool)GetValue(AutoFillBaseProperty); }
            set { SetValue(AutoFillBaseProperty, value); }
        }

        /// <summary>
        /// Indica se serao adicionados zeros a esqueda do dado caso o mesmo tenha menos caracteres do que o minimo do tipo
        /// </summary>
        protected static readonly DependencyProperty AutoFillBaseProperty =
            DependencyProperty.Register("AutoFillBase", typeof(bool), typeof(FixedMaskTextBox), new FrameworkPropertyMetadata(false));


        #endregion

        #region Construtores
        static FixedMaskTextBox()
        {
            TextProperty.OverrideMetadata(typeof(FixedMaskTextBox), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.NotDataBindable, OnTextPropertyChanged));
        }
        #endregion

        #region Metodos de Instancia

        protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
        {
            base.OnMouseDoubleClick(e);
            this.SelectAll();
        }

        //Metodo responsavel por cria o binding entre as propriedade Internal Value e Value
        //possibilitando assim o usu de validation rules
        protected override void CreateInternalBindings()
        {
            base.CreateInternalBindings();

            Binding binding = new Binding("Value");
            binding.Mode = BindingMode.TwoWay;
            binding.RelativeSource = new RelativeSource(RelativeSourceMode.Self);
            binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
            this.SetBinding(InternalValueProperty, binding);
        }

        protected string RemoveFormatChars(string text)
        {
            if (String.IsNullOrEmpty(text))
                return string.Empty;
            else
            {
                foreach (char separator in Separators)
                {
                    text = text.Replace(separator.ToString(), "");
                }
                return text;
            }
        }

        protected override void OnPasting(object sender, DataObjectPastingEventArgs e)
        {
            base.OnPasting(sender, e);

            string text = e.DataObject.GetData(DataFormats.Text, true) as string;

            if (!VerifyInputedText(text))
                e.CancelCommand();
        }

        //bloquear a exclusao dos caracteres de formatacao
        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            base.OnPreviewKeyDown(e);
            if (e.Key == Key.Delete)
            {
                if (this.CaretIndex < this.Text.Length &&
                    Separators.Contains(this.Text[this.CaretIndex].ToString()) &&
                    (this.SelectedText.Length == 0 || Separators.Contains(this.SelectedText[0].ToString()))
                    )
                {
                    this.CaretIndex++;
                    e.Handled = true;
                }
            }
            else if (e.Key == Key.Back)
            {
                if (this.CaretIndex > 0 &&
                    Separators.Contains(this.Text[this.CaretIndex - 1].ToString()) &&
                    (this.SelectedText.Length == 0 || Separators.Contains(this.SelectedText[0].ToString())))
                {
                    this.CaretIndex--;
                    e.Handled = true;
                }
            }
        }

        protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
        {
            if (this.IsInOverWriteMode)
            {
                if (this.SelectionLength == 0)
                    this.SelectionLength++;

                if (!string.IsNullOrEmpty(this.SelectedText) &&
                    Separators.Contains(this.SelectedText[0].ToString()))
                    this.SelectionStart++;
            }

            ///verificar o texto de entrada
            if (!VerifyInputedText(e.Text))
            {
                e.Handled = true;
                return;
            }

            //deve ser executado apos o codigo acima
            base.OnPreviewTextInput(e);
        }

        protected virtual bool VerifyInputedText(string text)
        {
            if (!String.IsNullOrEmpty(text))
            {
                //se foi digitado um separador, ignorar
                if (Separators.Contains(text[0].ToString()))
                {
                    return false;
                }
                else
                {
                    string previewText = PreviewTextValue(text);
                    previewText = RemoveFormatChars(previewText);
                    if (previewText.Length > ValueLength)
                    {
                        if (text.Length > RemoveFormatChars(this.SelectedText).Length)
                            return false;
                    }
                }
            }
            return true;
        }

        protected int GetRelativeCaretPosition()
        {
            if (!String.IsNullOrEmpty(this.Text))
                return this.RemoveFormatChars(this.Text.Substring(0, this.CaretIndex)).Length;
            else
                return 0;
        }

        protected void SetAbsoluteCaretPosition(int relativeCaretPosition)
        {
            int i = 0;

            if (relativeCaretPosition != 0)
            {
                bool stoped = false;
                for (i = 0; i < this.Text.Length; i++)
                {
                    if (Char.IsDigit(this.Text[i]))
                        relativeCaretPosition--;

                    if (relativeCaretPosition <= 0)
                    {
                        stoped = true;
                        break;
                    }
                }
                if (stoped)
                {
                    i++;
                    if (i < this.Text.Length && (Separators.Contains(this.Text[i].ToString())))
                    {
                        i++;
                    }
                }
            }

            this.CaretIndex = i;
        }

        protected virtual void ClearAll()
        {
            //recuperar os bindings
            Binding bValue = BindingOperations.GetBinding(this, FixedMaskTextBox.ValueProperty);
            Binding bText = BindingOperations.GetBinding(this, FixedMaskTextBox.TextProperty);
            Binding bInternalValue = BindingOperations.GetBinding(this, FixedMaskTextBox.InternalValueProperty);

            //limpar as properties
            this.ClearValue(ValueProperty);
            this.ClearValue(TextProperty);
            this.ClearValue(InternalValueProperty);

            //reatribuir o binding
            if (bValue != null)
                this.SetBinding(FixedMaskTextBox.ValueProperty, bValue);

            if (bText != null)
                this.SetBinding(FixedMaskTextBox.TextProperty, bText);

            if (bInternalValue != null)
                this.SetBinding(FixedMaskTextBox.InternalValueProperty, bInternalValue);
        }

        /// <summary>
        /// Método resposável por limpar os conteúdo do controle.
        /// </summary>
        public new void Clear()
        {
            base.Clear();
            this.ClearAll();
        }

        /// <summary>
        /// Metodo utilizado para validar a entrada do usuario
        /// equando esta digitando
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        protected bool VerifyValue(string text)
        {
            if (string.IsNullOrEmpty(text))
                return true;
            else if (text.Length > ValueLength)
                return false;
            else if (!MatchText(text))
                return false;
            else
                return true;
        }

        /// <summary>
        /// Metodo utilizado para validar a entrada de valores
        /// atravez da propriedade Value
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        protected virtual bool VerifyValueFormat(string text)
        {
            if (string.IsNullOrEmpty(text))
                return true;
            if (!MatchText(AssignedValueFormatExpression, text))
                return false;
            else
                return true;
        }

        protected void UpdateInternalValue(string text)
        {
            if (!string.IsNullOrEmpty(text))
                this.InternalValue = RemoveFormatChars(text);
            else
                this.InternalValue = string.Empty;
        }


        protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
        {
            //se a propridade autoFill estiver setada
            if (this.AutoFillBase)
            {
                this.Value = Fill(this.Value);
            }

            base.OnPreviewLostKeyboardFocus(e);
        }

        /// <summary>
        /// Preencher o texto passado via parametro com zeros a esquerda
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        protected virtual string Fill(string text)
        {
            if (!string.IsNullOrEmpty(text))
                return text.PadLeft(ValueLength, '0');
            else
                return text;
        }
        #endregion

        #region Metodos Abstrados
        protected abstract string Format(string text);
        #endregion

        #region Metodos Estaticos

        protected static object CoerceInternalValuePropery(DependencyObject d, object value)
        {
            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(d))
                return value;

            FixedMaskTextBox instance = d as FixedMaskTextBox;
            if (instance != null && value != null)
            {
                //se n for uma alteracao interna do value
                if (!instance.VerifyValue((string)value))
                {
                    instance.ClearAll();
                    throw new FormatException(FormatExceptionErroMessage);
                }
            }

            return value;
        }

        //protected static object CoerceValueProperty(DependencyObject d, object value)
        //{
        //    if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(d))
        //        return value;

        //    FixedMaskTextBox instance = d as FixedMaskTextBox;

        //    string strValue = string.Empty;
        //    if (value != null)
        //    {
        //        strValue = Convert.ToString(value);
        //        if (instance != null)
        //        {
        //            //caso o AutoFill esteja ativado, preencher a string de entrada com zeros para que o dado 
        //            //tenha o numero de caracteres definido para o tipo
        //            if (instance.AutoFillBase)
        //            {
        //                strValue = instance.Fill(strValue);
        //            }
        //        }
        //    }

        //    return strValue;
        //}


        private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(d))
                return;

            FixedMaskTextBox instance = d as FixedMaskTextBox;
            if (instance != null)
            {
                //se nao se trata de uma alteracao interna
                //ou seja, se trata de uma atribuicao externa
                if (!instance.IsInternalValueChanged)
                {
                    string value = Convert.ToString(e.NewValue);

                    //caso o AutoFill esteja ativado, preencher a string de entrada com zeros para que o dado 
                    //tenha o numero de caracteres definido para o tipo
                    if (instance.AutoFillBase)
                    {
                        value = instance.Fill(Convert.ToString(e.NewValue));
                    }
                    else
                    {
                        value = Convert.ToString(e.NewValue);
                    }

                    //verificar se o valor está correto
                    if (!instance.VerifyValueFormat(Convert.ToString(value)))
                    {
                        //instance.ClearAll();
                        throw new FormatException(FormatExceptionErroMessage);
                    }

                    instance.Value = value;
                    instance.InternalValue = Convert.ToString(value);
                }
            }
        }

        private static void OnInternalValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(d))
                return;

            FixedMaskTextBox instance = d as FixedMaskTextBox;
            if (instance != null)
            {
                int relativeCaretPosition;

                string formatedText = string.Empty;
                string text = Convert.ToString(e.NewValue);

                ///atualizar a propriedade value com o valor contido na propriedade internal value
                instance.IsInternalValueChanged = true;
                instance.Value = text;
                instance.IsInternalValueChanged = false;

                if (!string.IsNullOrEmpty((string)e.NewValue))
                {
                    formatedText = instance.Format((string)e.NewValue);
                }
                if (instance.Text != formatedText)
                {
                    instance.EnableInternalTextChange();
                    relativeCaretPosition = instance.GetRelativeCaretPosition();
                    instance.Text = formatedText;
                    instance.SetAbsoluteCaretPosition(relativeCaretPosition);
                }
            }
        }

        private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(d))
                return;

            FixedMaskTextBox instance = d as FixedMaskTextBox;
            if (instance != null)
            {
                instance.UpdateInternalValue((string)e.NewValue);
            }
        }

        #endregion

        #region ChangesMonitor
        /// <summary>
        /// Recuperar o binding para a propriedade de input do controle
        /// </summary>
        /// <returns></returns>
        protected override Binding GetBindingOfInputProperty()
        {
            return BindingOperations.GetBinding(this, FixedMaskTextBox.ValueProperty);
        }
        #endregion

        protected override void CreateValidationTriggers()
        {
            //nao deve chamar a classe base pois, caso contrario serao criados dois validation triggers para um mesmo evento
            //o que causa o nao funcionamento para um dos eventos
            if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
                return;

            EventValidationTrigger trigger = new EventValidationTrigger(ValidationTriggerScope.Self, LostFocusEvent, FixedMaskTextBox.InternalValueProperty);
            trigger.ContinueRoutingOnError = true;
            if (!this.ValidationTriggers.Contains(trigger))
            {
                this.ValidationTriggers.Add(trigger);
            }
        }

        /// <summary>
        /// Sobrescrever a criacao de validacoes da classe base para que a validacao passe a ser realizada
        /// na propriedade InternalTextProperty
        /// Solução adotada para resolver problema de validacao de campo requerido e demais validacoes
        /// </summary>
        protected override void CreateValidations()
        {
            //deve sobrescrever a classe base para que a validacao de obrigatoriedade seja feita na propriedade internal value
            if (!this.ValidationRules.Contains(RequiredValidationRuleName))
            {
                RequiredValidationRule reqValidation = new RequiredValidationRule(this, InternalValueProperty);
                reqValidation.Name = RequiredValidationRuleName;

                this.ValidationRules.Add(reqValidation);
                reqValidation.ErrorContent = this.RequiredErrorMessage;
                reqValidation.IsEnabled = this.IsRequired;
            }
        }
    }
}
